//--------------------------------------------------------------------
//>>>>>>>>>>>>>>>>>>>>>>>>> COPYRIGHT NOTICE <<<<<<<<<<<<<<<<<<<<<<<<<
//--------------------------------------------------------------------
//Copyright (c) 2001 - 2008 by Lattice Semiconductor Corporation
//--------------------------------------------------------------------
//Permission:
//Lattice Semiconductor grants permission to use this code for use
//in synthesis for any Lattice programmable logic product. Other
//use of this code, including the selling or duplication of any
//portion is strictly prohibited.
//Disclaimer:
//This VHDL or Verilog source code is intended as a design reference
//which illustrates how these types of functions can be implemented.
//It is the user's responsibility to verify their design for
//consistency and functionality through the use of formal
//verification methods. Lattice Semiconductor provides no warranty
//regarding the use or functionality of this code.
//--------------------------------------------------------------------
//Lattice Semiconductor Corporation
//5555 NE Moore Court
//Hillsboro, OR 97214
//U.S.A
//TEL: 1-800-Lattice (USA and Canada)
//503-268-8001 (other locations)
//web: http://www.latticesemi.com/
//email: techsupport@latticesemi.com
//     chao.tan@latticesemi.com
//--------------------------------------------------------------------
//module description
//--------------------------------------------------------------------
//   This module implements an I2C slave controller to access the
// internal registers. Only 1 byte data can be accessed per read/write
// course. Burst transfers for read/write are not support.
//--------------------------------------------------------------------
//Code Revision History :
//--------------------------------------------------------------------
//Ver: | Author | Mod. Date  |Changes Made:
//V1.0 |  Kile  |  15/07/09  |Initial ver
//V2.0 | D.Wang | 10/15/2012 | Add Read_Done signal
//--------------------------------------------------------------------
`timescale 1ns/1ps
`ifdef SIM_ONLY
`define CLK2Q   #0.5
`else
`define CLK2Q
`endif

module i2c_slave #(
   parameter DEVICE_ID = 7'h00,     // Slave address.
   parameter OE_DELAY_CYCLE = 8'h3) // delay cycles for sda_oe output.
  (
  rstn,         // Active low asynchronous reset.
  sys_clk,      // internal clock
  rd_continue,  // read_continue mode
  rd_num,       // the number of the reading data
  scl,          // I2C clock input.
  sda,          // I2C data input.
  sda_oe,       // sda output low enable. Attach to open collector.
  dev_id,       // device ID received from master
  reg_addr,     // 8-bit register address.
  rd_data,      // 8-bit data being read from the register.
  wr_data,      // 8-bit data being written to the register.
  wr_en,        // Register write_enable.
  mstr_rd_done  // Read data has been transferred
  ) ;


/******************************************************************************
 * I/O Ports Declarations
 ******************************************************************************/

input        rstn;
input        sys_clk;
input        rd_continue;
input  [7:0] rd_num;
input        scl;
input        sda;
output [6:0] dev_id;
output       sda_oe;
output [7:0] reg_addr;
input  [7:0] rd_data;
output [7:0] wr_data;
output       wr_en;
output       mstr_rd_done;

reg    [6:0] dev_id;
reg          sda_oe;
reg    [7:0] reg_addr;
reg          wr_en;
reg          mstr_rd_done;

wire   [7:0] wr_data;

/******************************************************************************
 * Internal reg/wire Declarations
 ******************************************************************************/

reg [3:0] scl_tmp_d;
reg [3:0] sda_tmp_d;
reg       start;
reg       stop;
reg [2:0] cnt;
reg       cnt_en;
reg       load;
reg [7:0] shift;
reg [3:0] state;
reg [3:0] state_nxt;
reg       low_oe;  // sda output low
reg       capture;
reg [7:0] delay_cnt;
reg       oe_d1;
reg [7:0] rd_cnt;
reg       rd_last_one;

//wire      scl_rising;
wire      scl_falling;
wire      scl_one;
//wire      scl_zero;
wire      sda_rising;
wire      sda_falling;
wire      sda_one;
//wire      sda_zero;
wire      cnt_last;

parameter IDLE         = 4'h0;
parameter DEV_ID_CHK   = 4'h1;
parameter DEV_ID_ACK   = 4'h2;
parameter RD_DATA      = 4'h3;
parameter RD_ACK       = 4'h4;
parameter WR_ADDR      = 4'h5;
parameter WR_ADDR_ACK  = 4'h6;
parameter WR_DATA      = 4'h7;
parameter WR_DATA_ACK  = 4'h8;

/******************************************************************************
 * scl sync : get rid of glitches shorter than 1 sys_clk.
 ******************************************************************************/

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    scl_tmp_d <= `CLK2Q 4'hf;
  else
    scl_tmp_d <= `CLK2Q { scl_tmp_d[2:0], scl };
  end

//assign scl_rising = ( { scl_tmp_d[2:0], scl } == 4'b0011 ) | ( { scl_tmp_d, scl } == 5'b00101 );
assign scl_falling = ( { scl_tmp_d[2:0], scl } == 4'b1100 ) | ( { scl_tmp_d, scl } == 5'b11010 );

assign scl_one = ( scl_tmp_d[1:0] == 2'b11 ) | ( scl_tmp_d[1] && scl ) | ( scl_tmp_d[0] && scl );
//assign scl_zero = ( scl_tmp_d == 2'b00 ) | ( !scl_tmp_d[1] && !scl ) | ( !scl_tmp_d[0] && !scl );

/******************************************************************************
 * sda sync : get rid of glitches shorter than 1 sys_clk.
 ******************************************************************************/

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    sda_tmp_d <= `CLK2Q 4'hf;
  else
    sda_tmp_d <= `CLK2Q { sda_tmp_d[2:0], sda };
  end

assign sda_rising = ( { sda_tmp_d[2:0], sda } == 4'b0011 ) | ( { sda_tmp_d, sda } == 5'b00101 );
assign sda_falling = ( { sda_tmp_d[2:0], sda } == 4'b1100 ) | ( { sda_tmp_d, sda } == 5'b11010 );

assign sda_one = ( sda_tmp_d[1:0] == 2'b11 ) | ( sda_tmp_d[1] && sda ) | ( sda_tmp_d[0] && sda );
//assign sda_zero = ( sda_tmp_d == 2'b00 ) | ( !sda_tmp_d[1] && !sda ) | ( !sda_tmp_d[0] && !sda );

/******************************************************************************
 * Start/Stop Condition:
 * 1. The start condition is a high-to-low transition on SDA while SCL is high.
 * 2. The stop condition is a low-to-high transition on SDA while SCL is high.
 * 3. The start/stop conditions are cleared at each SCL falling edge.
 ******************************************************************************/

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    start <= `CLK2Q 1'h0;
  else if ( scl_falling )
    start <= `CLK2Q 1'h0;
  else if ( sda_falling )
    start <= `CLK2Q scl_one;
  end

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    stop <= `CLK2Q 1'h0;
  else if ( scl_falling )
    stop <= `CLK2Q 1'h0;
  else if ( sda_rising )
    stop <= `CLK2Q scl_one;
  end


/******************************************************************************
 * Data bit counter for DEV_ID_CHK/RD_DATA/WR_ADDR/WR_DATA.
 ******************************************************************************/

assign cnt_last = ( & cnt );

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    cnt <= 3'd0;
  else if ( start )
    cnt <= 3'd0;
  else if ( cnt_en && scl_falling )
    cnt <= cnt + 1'b1;
  end


/******************************************************************************
 * Shift register for in/out data shifting.
 ******************************************************************************/

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    shift <= 8'h00;
  else if ( load && scl_falling )
    shift <= rd_data;
  else if ( cnt_en && scl_falling )
    shift <= { shift[6:0], sda_one };
  end

assign wr_data = shift;


/******************************************************************************
 * State Machine:
 ******************************************************************************/

always @(*) begin

  cnt_en    = 1'b0;
  low_oe    = 1'b0;
  load      = 1'b0;
  capture   = 1'b0;
  state_nxt = state;

  case( state )

    DEV_ID_CHK: begin   // Get the slave address.
      cnt_en = 1'b1;
      if( start )
        state_nxt = DEV_ID_CHK;
      else if( stop )
        state_nxt = IDLE;
      else if( cnt_last )
        state_nxt = ( shift[6:0] == DEVICE_ID[6:0] ) ? DEV_ID_ACK : IDLE;
      end

    DEV_ID_ACK: begin // Acknowledge the device selection.
      low_oe    = 1'b1;
      load      = shift[0];
      if( start )
        state_nxt = DEV_ID_CHK;
      else if( stop )
        state_nxt = IDLE;
      else
        state_nxt = shift[0] ? RD_DATA : WR_ADDR;
      end

    RD_DATA: begin // Send data.
      cnt_en = 1'b1;
      if( !shift[7] )
        low_oe = 1'b1;
      if( start )
        state_nxt = DEV_ID_CHK;
      else if( stop )
        state_nxt = IDLE;
      else if( cnt_last )
        state_nxt = RD_ACK;
      end

    RD_ACK: begin  // Wait for master's respond.
      if ( rd_continue ) begin
        if ( rd_last_one )
          load = 1'b0;
        else
          load = 1'b1;
      end
      if ( start )
        state_nxt = DEV_ID_CHK;
      else if ( scl_falling & sda_one )
        state_nxt = IDLE;
      else if ( rd_continue ) begin
        if ( rd_last_one )
          state_nxt = IDLE;
        else
          state_nxt = RD_DATA;
      end
      else
        state_nxt = IDLE;
      end

    WR_ADDR: begin // Get the register's address to be written into.
      cnt_en = 1'b1;
      if( start )
        state_nxt = DEV_ID_CHK;
      else if( stop )
        state_nxt = IDLE;
      else if( cnt_last )
        state_nxt = WR_ADDR_ACK;
      end

    WR_ADDR_ACK: begin // Acknowledge the write address.
      capture   = 1'b1;
      low_oe    = 1'b1;
      if( start )
        state_nxt = DEV_ID_CHK;
      else if( stop )
        state_nxt = IDLE;
      else
        state_nxt = WR_DATA;
      end

    WR_DATA: begin // Get the register's value to be updated.
      cnt_en = 1'b1;
      if( start )
        state_nxt = DEV_ID_CHK;
      else if( stop )
        state_nxt = IDLE;
      else if( cnt_last )
        state_nxt = WR_DATA_ACK;
      end

    WR_DATA_ACK: begin  // Update the register.
      low_oe    = 1'b1;
      if( start )
        state_nxt = DEV_ID_CHK;
      else
        state_nxt = IDLE;
      end

//    IDLE:
    default: begin
      if( start )
        state_nxt = DEV_ID_CHK;
      end

    endcase

  end

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    rd_last_one <= 0;
  else if ( scl_falling ) begin
    if ( state == IDLE )
      rd_last_one <= 0;
    else if ( rd_continue )
      rd_last_one <= ( rd_cnt == rd_num );
    end
  end

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    state <= IDLE;
  else if ( scl_falling )
    state <= state_nxt;
  end

/******************************************************************************
 * Address register for write_address saving.
 ******************************************************************************/

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    reg_addr <= 8'h00;
  else if ( capture && scl_falling )
    reg_addr <= shift;
  // read_continue mode : address automaticly increase 1
  else if ( rd_continue && ( state == RD_DATA ) && cnt_last && scl_falling )
    reg_addr <= reg_addr + 1;
  end

/******************************************************************************
 * read_counter register : counter the data number having been read
 ******************************************************************************/

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    rd_cnt <= 8'd0;
  else if ( state == DEV_ID_CHK )
    rd_cnt <= 8'd0;
  // read_continue mode : address automaticly increase 1
  else if ( rd_continue && ( state == RD_DATA ) && cnt_last && scl_falling )
    rd_cnt <= rd_cnt + 1;
  end

/******************************************************************************
 * delay_cnt register : counter the cycles after the falling edge of SCL.
 ******************************************************************************/

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    delay_cnt <= 8'd0;
  else if ( delay_cnt == OE_DELAY_CYCLE )
    delay_cnt <= 8'd0;
  else if ( scl_falling )
    delay_cnt <= 8'd1;
  else if ( delay_cnt != 8'd0 )
    delay_cnt <= delay_cnt + 1;
  end


/******************************************************************************
 * oe_d1 register : latch register low_oe.
 ******************************************************************************/

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    oe_d1 <= 0;
  else
    oe_d1 <= low_oe;
  end


/******************************************************************************
 * sda_oe register : sda output low enable with OE_DELAY_CYCLE cycles delay
 *                   after the falling edge of SCL.
 ******************************************************************************/

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    sda_oe <= 0;
  else if ( delay_cnt == OE_DELAY_CYCLE )
    sda_oe <= oe_d1;
  end


/******************************************************************************
 * wr_en register : register write_enable with OE_DELAY_CYCLE cycles delay
 *                  after having captured the register value to be updated.
 ******************************************************************************/

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    wr_en <= 0;
  else if ( state == WR_DATA_ACK )
    wr_en <= ( delay_cnt == OE_DELAY_CYCLE );
  else
    wr_en <= 0;
  end

/******************************************************************************
 * dev_id : record DEVICE_ID.
 ******************************************************************************/

always @( negedge rstn or posedge sys_clk ) begin
  if( !rstn )
    dev_id <= `CLK2Q 7'd0;
  else if ( ( state == DEV_ID_CHK ) && cnt_last )
    dev_id <= `CLK2Q shift[6:0];
  end

/*****************************************************************************
 * mstr_rd_done : Master Read Operation is Done
 *****************************************************************************/
 always @(negedge rstn or posedge sys_clk) begin
    if (~rstn)
       mstr_rd_done <= 0;
    else if ((state == RD_ACK) && scl_falling && (!sda_one))
       mstr_rd_done <= 1'b1;
    else
       mstr_rd_done <= 0;
 end
 
endmodule // ql_i2cs

